{
 "metadata": {
  "name": "",
  "signature": "sha256:b0d306237b67c3196151d5971df84022c7fec4e0308daccd87a7f61af99f1128"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Outline\n",
      "- why logging?\n",
      "- basic logging\n",
      "- elements of the logging package\n",
      "- advanced logging\n",
      " - a dummy software package\n",
      " - class-specific logging\n",
      " - global logging\n",
      " - logging config"
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Why Logging?\n",
      "\n",
      "Python's `logging` module allows you to easily categorize, filter, direct, and format log messages. With a bit of forethought, you can avoid a lot of bookkeeping work."
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Basic Logging\n",
      "You've likely seen some of this before.\n",
      "\n",
      "Before we go anywhere, remember the *severity* or *level* hierarchy, ordered by diminishing severity:\n",
      "\n",
      " - FATAL\n",
      " - CRITICAL\n",
      " - ERROR\n",
      " - WARNING\n",
      " - INFO\n",
      " - DEBUG"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "import logging\n",
      "\n",
      "logging.info(\"test info msg\")\n",
      "logging.warning(\"test warning msg\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "In the two call just made, one printed its message to STDOUT, while the other seemed to do nothing. What's going on?\n",
      "\n",
      "Calls the the module-level functions `debug`, `info`, `warning`, etc make implicit calls to the function `logging.basicConfig`. This initiates the \"root logger\", and configures it to do a minimal set of useful things, including streaming certain messages to the console. The default severity level (below which messages are not passed on) is WARNING, which explains why the INFO-level message was not displayed.\n",
      "\n",
      "Let's explicitly access the root logger and change the level.\n",
      "\n",
      "Logger objects are NEVER initialized explicitly. Instead, specific loggers are generated by a factory function: `logging.getLogger(MY_LOGGER_NAME)`. Subsequent calls to the same function from within the same Python application will always return the SAME Logger object."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# get a named logger from the logger factory\n",
      "logr = logging.getLogger(\"root\")\n",
      "\n",
      "# note: \"getLogger\"\n",
      "# - is a module-level function\n",
      "# - always returns the same object for a given argument\n",
      "# - is camel-cased"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# let's see what this thing is\n",
      "dir(logr)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Now we'll set its level and test.\n",
      "logr.setLevel(logging.INFO) \n",
      "# equivalently:\n",
      "# logr.setLevel(\"INFO\")\n",
      "\n",
      "logr.info(\"test info msg\")\n",
      "logging.warning(\"test warning msg\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Great! It works. You've now seen a little demo of the default (root) logger, its severity filter, and how to change it.\n",
      "\n",
      "Note the tricksery: I called the `info` method of the Logger object named `logr`, but I called the `warning` method of the module called `logging`. Yet they both were handled by the same (root) logger.\n",
      "\n",
      "\n",
      "# Elements of the Logging Package\n",
      "\n",
      "- [LogRecord](https://docs.python.org/2/library/logging.html#logrecord-attributes) objects: encapsulate a logging message and its attributes like timestamp, level, etc.\n",
      "- [Logger](https://docs.python.org/2/library/logging.html#logger-objects) objects: containers for LogRecord, which are retained or dropped according to the Logger's configuration. Loggers can have hierarchical relationships. There also exists a special RootLogger class.\n",
      "- [Filter](https://docs.python.org/2/library/logging.html#filter-objects) objects: implement the rejection of LogRecord objects in Loggers, based on LogRecord level OR other criteria.\n",
      "- [Handler](https://docs.python.org/2/library/logging.handlers.html#module-logging.handlers) objects: direct messages to outputs; affiliated with one or more Loggers; one base class, many derived classes, such as StreamHandler and FileHandle\n",
      "- [Formatter](https://docs.python.org/2/library/logging.html#formatter-objects) objects: affiliated with one or more Handlers; they define the output content and format\n",
      "\n",
      "\n",
      "You can think of the message path as a series of possible rejections: "
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from IPython.display import Image\n",
      "Image(\"https://docs.python.org/2/_images/logging_flow.png\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "As mentioned previously, the `logging.basicConfig` function configures a Logger object named `root`, and affiliates it with a Handler and that Handler with a Formatter. \n",
      "\n",
      "Let's explicitly build a system that replicates this behavior: "
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Start by building the objects\n",
      "\n",
      "# get a logger from the factory\n",
      "snarf = logging.getLogger(\"my_logger_named_snarf\")\n",
      "snarf.setLevel(logging.INFO)\n",
      "# this will be discussed later\n",
      "snarf.propagate = False\n",
      "# make a handler; default StreamHandler behavior is to stream the output to STDOUT\n",
      "snarfs_handler = logging.StreamHandler() \n",
      "# NOTE: level-based filters can be set in the handler as well as the logger\n",
      "snarfs_handler.setLevel(logging.WARNING)\n",
      "# make a formatter with the magic syntax sauce\n",
      "magic_syntax_sauce = \"%(levelname)s:%(name)s:%(message)s\"\n",
      "snarfs_handlers_formatter = logging.Formatter(magic_syntax_sauce)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# ipython-specific digression: remove all handlers \n",
      "\n",
      "## This is only relevant in an environment (like ipython) in which commands get run multiple times \n",
      "## in the same python process. Restart the kernel to reset the logging environment.\n",
      "snarf.handlers = []"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# associate everything\n",
      "snarfs_handler.setFormatter(snarfs_handlers_formatter)\n",
      "snarf.addHandler(snarfs_handler)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# we should see only the WARNING message, since that's the setting in the handler\n",
      "snarf.debug(\"test debug\")\n",
      "snarf.info(\"test info\")\n",
      "snarf.warning(\"test warning\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Yeah!"
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Advanced Logging\n",
      "\n",
      "Let's do the following:\n",
      "\n",
      "- create an environment in which a variety of objects are initialized\n",
      "- associate loggers with each of these objects\n",
      "- create a global logger, which is hierarchically related to the object loggers\n",
      "- send all log messages from all loggers to logger-specific files\n",
      "- print WARNING and more severe messages to the screen "
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "## Create a software \"package\"\n",
      "\n",
      "Inspired by the `python-oop` tutorial in this repo, let's create a class heirachy around pets. "
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "import sys\n",
      "import logging\n",
      "\n",
      "class Pet(object):\n",
      "    def __init__(self, **kwargs):\n",
      "        # attach all keyword arguments\n",
      "        [setattr(self,k,v) for k,v in kwargs.items()]\n",
      "        # get the class name\n",
      "        self.animal = self.__class__.__name__\n",
      "        assert self.animal != \"Pet\"     \n",
      "\n",
      "    def talk(self):\n",
      "        sys.stdout.write(\"{}\\n\".format(self.word))\n",
      "        \n",
      "    def get_bio(self):\n",
      "        sys.stdout.write(\"This pet is a {0} named {1}. It has {2} legs.\\n\".format(self.animal,self.name,self.legs))\n",
      "        \n",
      "class Cat(Pet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Tom\"\n",
      "        self.word = \"Meow\"\n",
      "        self.legs = 4\n",
      "        \n",
      "        super(Cat,self).__init__(**kwargs)\n",
      "        \n",
      "class Dog(Pet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Fido\"\n",
      "        self.word = \"Arf!\"\n",
      "        self.legs = 4\n",
      "        \n",
      "        super(Dog,self).__init__(**kwargs)\n",
      "              \n",
      "class Snake(Pet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Voltemort\"\n",
      "        self.word = \"Sssss\"\n",
      "        self.legs = 0\n",
      "        \n",
      "        super(Snake,self).__init__(**kwargs)\n",
      "                "
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Now we create a few pet objects."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "c = Cat()\n",
      "c.get_bio()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "d = Dog(name=\"Lassie\")\n",
      "d.get_bio()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# This command is commented-out, so that the \"run all\" option works.\n",
      "\n",
      "#p = Pet(name=\"Mr. Centipede\",legs=100)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "s_1 = Snake()\n",
      "s_2 = Snake(word=\"Ss\")\n",
      "s_1.get_bio()\n",
      "s_1.talk()\n",
      "print(\"The second snake says: {}\".format(s_2.word))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# helper function \n",
      "def the_pets_speak(list_of_pets):\n",
      "    sys.stdout.write(\"The pets will speak now:\\n\")\n",
      "    for pet in list_of_pets:\n",
      "        pet.talk()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "list_of_pets = [c,d,s_1,s_2]\n",
      "the_pets_speak(list_of_pets)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "## Add loggers to objects"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# a new base class that is derived from the old base class \n",
      "class LoggablePet(Pet):\n",
      "    def __init__(self, **kwargs):\n",
      "\n",
      "        # call the constructor of the parrent class first\n",
      "        super(LoggablePet,self).__init__(**kwargs)\n",
      "        \n",
      "        # now make a logger that is named by the animal and its name\n",
      "        self.logger_name = \"pet_world.\" + self.animal + \".\" + self.name\n",
      "        self.logger = logging.getLogger(self.logger_name)\n",
      "\n",
      "# The only change below here is to inherit from LoggablePet, instead of Pet\n",
      "class Cat(LoggablePet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Tom\"\n",
      "        self.word = \"Meow\"\n",
      "        self.legs = 4\n",
      "        super(Cat,self).__init__(**kwargs)\n",
      "        \n",
      "class Dog(LoggablePet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Fido\"\n",
      "        self.word = \"Arf!\"\n",
      "        self.legs = 4\n",
      "        super(Dog,self).__init__(**kwargs)\n",
      "              \n",
      "class Snake(LoggablePet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Voltemort\"\n",
      "        self.word = \"Sssss\"\n",
      "        self.legs = 0\n",
      "        super(Snake,self).__init__(**kwargs)\n",
      "\n",
      "                "
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "## Understanding defaults and the root logger\n",
      "\n",
      "Let's send a test INFO-level message to a cat."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "c = Cat(name=\"Boots\")\n",
      "c.logger.debug(\"test debug message\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "[crickets] \n",
      "\n",
      "(...unless you're rerunning this cell after making changes to the logger in cells below; remember, loggers are persisted for the life of the kernel and getLogger will always return the same object given the same argument...)\n",
      "\n",
      "We need to change the level of the logger, so it passes on a DEBUG message."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "c.logger.setLevel(logging.DEBUG)\n",
      "c.logger.debug(\"test debug message\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "We've added no handlers to the logger, yet the message was handled by a StreamHandler with a basic Formatter set. How did this happen? "
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "c.logger.parent"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Aha! the default parent logger is a RootLogger object with a default StreamHandler and Formatter configured. We can change this behavior in two ways:\n",
      " - turn of the `propagate` flag, meaning that messages are not passed to parent loggers\n",
      " - remove the handler from the RootLogger parent"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "## handler-removing method\n",
      "\n",
      "# save the root logger's handler\n",
      "stream_handler = c.logger.parent.handlers[0]\n",
      "# empty the handlers list\n",
      "c.logger.parent.handlers = []\n",
      "# and test!\n",
      "c.logger.debug(\"test debug message\")\n",
      "\n",
      "# add it back, if you wish\n",
      "#c.logger.parent.addHandler(stream_handler)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "## change the propagate flag\n",
      "\n",
      "#c.logger.propagate = False\n",
      "#c.logger.debug(\"test debug message\")\n",
      "#c.logger.propagate = True"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Remember that we've left the root logger with no handlers.\n",
      "\n",
      "\n",
      "## Build the handlers and formatters\n",
      "\n",
      "Now, let's try to meet the specifications from the top of the section.\n",
      "- associate loggers with each of the Pet objects\n",
      "- send all log messages from all loggers to logger-specific files\n",
      "\n",
      "Start by making a FileHandler."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "file_path = \"./logs/\"\n",
      "# delete all the files at this location\n",
      "!rm ./logs/*\n",
      "\n",
      "log_file_name = file_path + c.logger_name + \".log\"\n",
      "file_handler = logging.FileHandler(log_file_name)\n",
      "c.logger.addHandler(file_handler) # FYI: adding the same handler a second time has no effect\n",
      "c.logger.debug(\"test debug message\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Check for a log file at the specified path. \n",
      "\n",
      "Also notice that the log file format is a bit unhelpful. "
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "better_magic_syntax_sauce = \"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"\n",
      "file_formatter = logging.Formatter(better_magic_syntax_sauce)\n",
      "file_handler.setFormatter(file_formatter)\n",
      "c.logger.debug(\"test debug message\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Also note that the default level of the handler must be DEBUG, because it printed out message. Let's test that:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "file_handler.setLevel(logging.INFO)\n",
      "c.logger.debug(\"shouldn't see this DEBUG message\")\n",
      "c.logger.info(\"should see this INFO message\")\n",
      "\n",
      "# and set it back\n",
      "file_handler.setLevel(logging.DEBUG)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Now that we have a good handler and formatter, let's just build them into the class definitions. Let's also add some built-in logging."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class LoggablePet(Pet):\n",
      "    def __init__(self, **kwargs):\n",
      "\n",
      "        # call the constructor of the parrent class first\n",
      "        super(LoggablePet,self).__init__(**kwargs)\n",
      "        \n",
      "        # set up logger\n",
      "        self.logger_name = \"pet_world.\" + self.animal + \".\" + self.name\n",
      "        self.logger = logging.getLogger(self.logger_name)\n",
      "        self.logger.setLevel(logging.DEBUG)\n",
      "        \n",
      "        # set up handler\n",
      "        self.log_file_name = file_path + self.logger_name + \".log\"\n",
      "        self.file_handler = logging.FileHandler(self.log_file_name)\n",
      "        self.file_handler.setLevel(logging.DEBUG)\n",
      "        self.logger.addHandler(self.file_handler)\n",
      "        \n",
      "        # add formatter\n",
      "        self.file_handler.setFormatter(file_formatter)\n",
      "        \n",
      "        # built-in logging\n",
      "        self.logger.info(\"{} is ready for play!\".format(self.name))\n",
      "\n",
      "# no changes below here\n",
      "\n",
      "class Cat(LoggablePet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Tom\"\n",
      "        self.word = \"Meow\"\n",
      "        self.legs = 4\n",
      "        super(Cat,self).__init__(**kwargs)\n",
      "        \n",
      "class Dog(LoggablePet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Fido\"\n",
      "        self.word = \"Arf!\"\n",
      "        self.legs = 4\n",
      "        super(Dog,self).__init__(**kwargs)\n",
      "              \n",
      "class Snake(LoggablePet):\n",
      "    def __init__(self, **kwargs):\n",
      "        # default values\n",
      "        self.name = \"Voltemort\"\n",
      "        self.word = \"Sssss\"\n",
      "        self.legs = 0\n",
      "        super(Snake,self).__init__(**kwargs)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "d = Dog()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Log message looks good in file."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "s_1 = Snake()\n",
      "s_2 = Snake(name=\"Lucifer\")\n",
      "\n",
      "# Be careful with pets of the same name and animal type but different attributes!"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Now let's meet the other specifications from above; namely, a parent logger that prints WARNING and above to the screen, while printing everything to a file.\n",
      "\n",
      "IMPORTANT: Hierarchies of loggers (apart from the RootLogger) are defined by period-separated logger names. For example, a logger named \"abc.def\" is automatically a child of a logger named \"abc\". This is why all the Pet object logger names start with \"pet_world\"."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# get the parent logger and set its base level\n",
      "global_logger = logging.getLogger(\"pet_world\")\n",
      "global_logger.setLevel(logging.DEBUG)\n",
      "\n",
      "# make a file handler\n",
      "global_file_handler = logging.FileHandler(file_path + \"global.log\")\n",
      "global_file_handler.setFormatter(file_formatter)\n",
      "\n",
      "# make a stream handler\n",
      "global_stream_handler = logging.StreamHandler()\n",
      "global_stream_handler.setLevel(logging.WARNING)\n",
      "\n",
      "# add the handlers\n",
      "global_logger.handlers = []\n",
      "global_logger.addHandler(global_file_handler)\n",
      "global_logger.addHandler(global_stream_handler)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "global_logger.info(\"info\")\n",
      "global_logger.warning(\"warning\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "d.logger.info(\"some doggy info\")\n",
      "d.logger.warning(\"a doggy warning!\")"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "So, WARNING and above messages are printed to the screen not only for the global logger, but also for its children (i.e. any loggers with names that start with \"pet_world\"). Additionally, each logger prints every message to a file.\n",
      "\n",
      "Yay! We built the specified logging system."
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "## Logger Configuration (Bonus)\n",
      "\n",
      "### Background: \n",
      "\n",
      "Most libraries that include logging use loggers defined at the *module* level. Therefore, a commonly-used module naming convention is: \n",
      "\n",
      "`logger = logging.getLogger(__name__)`\n",
      "\n",
      "The benefit of this is that all loggers can be determined at compile-time (rather than run-time). To investigate this paradigm, each class has been put in a separate module. \n",
      "\n",
      "### The Final Exercise\n",
      "The last task in the tutorial is to investigate the logging configuration file found in the local directory, along with the pet modules and the `run.py` script."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [],
     "language": "python",
     "metadata": {},
     "outputs": []
    }
   ],
   "metadata": {}
  }
 ]
}